﻿\newcommand{\ARMELF}{\InSqBrackets{\IT{ELF for the ARM 64-bit Architecture (AArch64)}, (2013)}\footnote{\AlsoAvailableAs \url{http://go.yurichev.com/17288}}}

\subsection{Релоки в ARM64}
\label{ARM64_relocs}

Как известно, в ARM64 инструкции 4-байтные, так что записать длинное число в регистр одной инструкцией нельзя.

Тем не менее, файл может быть загружен по произвольному адресу в памяти, для этого релоки и нужны.

Больше о них (в связи с Win32 PE): \myref{subsec:relocs}.

\myindex{ARM!\Instructions!ADRP/ADD pair}
В ARM64 принят следующий метод: адрес формируется при помощи пары инструкций: \TT{ADRP} и \ADD.

Первая загружает в регистр адрес 4KiB-страницы, а вторая прибавляет остаток.

Скомпилируем пример из \q{\HelloWorldSectionName} 
(\lstref{hw_c}) в GCC (Linaro) 4.9 под win32:

\begin{lstlisting}[caption=GCC (Linaro) 4.9 и objdump объектного файла,style=customasmARM]
...>aarch64-linux-gnu-gcc.exe hw.c -c

...>aarch64-linux-gnu-objdump.exe -d hw.o

...

0000000000000000 <main>:
   0:   a9bf7bfd        stp     x29, x30, [sp,#-16]!
   4:   910003fd        mov     x29, sp
   8:   90000000        adrp    x0, 0 <main>
   c:   91000000        add     x0, x0, #0x0
  10:   94000000        bl      0 <printf>
  14:   52800000        mov     w0, #0x0                        // #0
  18:   a8c17bfd        ldp     x29, x30, [sp],#16
  1c:   d65f03c0        ret

...>aarch64-linux-gnu-objdump.exe -r hw.o

...

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000008 R_AARCH64_ADR_PREL_PG_HI21  .rodata
000000000000000c R_AARCH64_ADD_ABS_LO12_NC  .rodata
0000000000000010 R_AARCH64_CALL26  printf
\end{lstlisting}

Итак, в этом объектом файле три релока.

\begin{itemize}
\item 
Самый первый берет адрес страницы, отсекает младшие 12 бит и записывает оставшиеся старшие 21
в битовые поля инструкции \TT{ADRP}. Это потому что младшие 12 бит кодировать не нужно,
и в ADRP выделено место только для 21 бит.

\item Второй ---- 12 бит адреса, относительного от начала страницы, в поля инструкции \ADD.

\item Последний, 26-битный, накладывается на инструкцию по адресу \TT{0x10}, где переход на функцию \printf.

Все адреса инструкций в ARM64 (да и в ARM в режиме ARM) имеют нули в двух младших битах
(потому что все инструкции имеют размер в 4 байта),
так что нужно кодировать только старшие 26 бит из 28-битного адресного пространства ($\pm 128$MB).

\end{itemize}

В слинкованном исполняемом файле релоков в этих местах нет: потому что там уже точно известно, 
где будет находится строка \q{Hello!}, и в какой странице, а также известен адрес функции \puts.

И поэтому там, в инструкциях \TT{ADRP}, \ADD и \TT{BL}, уже проставлены нужные значения 
(их проставил линкер во время компоновки):

\begin{lstlisting}[caption=objdump исполняемого файла,style=customasmARM]
0000000000400590 <main>:
  400590:       a9bf7bfd        stp     x29, x30, [sp,#-16]!
  400594:       910003fd        mov     x29, sp
  400598:       90000000        adrp    x0, 400000 <_init-0x3b8>
  40059c:       91192000        add     x0, x0, #0x648
  4005a0:       97ffffa0        bl      400420 <puts@plt>
  4005a4:       52800000        mov     w0, #0x0                        // #0
  4005a8:       a8c17bfd        ldp     x29, x30, [sp],#16
  4005ac:       d65f03c0        ret

...

Contents of section .rodata:
 400640 01000200 00000000 48656c6c 6f210000  ........Hello!..
\end{lstlisting}

\myindex{ARM!\Instructions!BL}

В качестве примера, попробуем дизассемблировать инструкцию BL вручную.\\
\TT{0x97ffffa0} это $0b10010111111111111111111110100000$.
В соответствии с \InSqBrackets{\ARMSixFourRef C5.6.26}, \IT{imm26} это последние 26 бит:\\
$imm26 = 0b11111111111111111110100000$.
Это \TT{0x3FFFFA0}, но \ac{MSB} это 1, 
так что число отрицательное, мы можем вручную его конвертировать в удобный для нас вид.
По правилам изменения знака (\myref{sec:signednumbers:negation}), просто инвертируем все биты: (\TT{0b1011111=0x5F}) и прибавляем 1 (\TT{0x5F+1=0x60}).
Так что число в знаковом виде: \TT{-0x60}.
Умножим \TT{-0x60} на 4 (потому что адрес записанный в опкоде разделен на 4): это \TT{-0x180}.
Теперь вычисляем адрес назначения: \TT{0x4005a0} + (\TT{-0x180}) = \TT{0x400420} 
(пожалуйста заметьте: мы берем адрес инструкции BL, а не текущее значение \ac{PC}, которое может быть другим!).
Так что адрес в итоге \TT{0x400420}.\\
\\
Больше о релоках связанных с ARM64: \ARMELF.
